package com.chris.tinyurl.biz.tinyurl.impl;

import com.chris.tinyurl.biz.data.TinyUrl;
import com.chris.tinyurl.biz.id.IdGenerator;
import com.chris.tinyurl.biz.tinyurl.TinyUrlConverter;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalListener;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * 采用生成自增ID，转换成62进制生成短域名的方式处理
 * 只要ID不超过 281474976710656 62的8次方，就可以保证位数在8位，由于不考虑
 * 多机环境或分布式环境（分布式环境映射不能存在本机内存，如果分布式环境，需要一个全局自增ID，可以全局发号或数据库自增-非分库分表）
 * 真实线上环境应该将tinyUrl映射关系存储到DB或NoSQL中
 */
public class DefaultTinyUriConverter implements TinyUrlConverter, InitializingBean {

    private IdGenerator idGenerator;

    public DefaultTinyUriConverter(IdGenerator idGenerator) {
        this.idGenerator = idGenerator;
    }

    @Value("${tinyurl.cached.max:5000000}")
    private int maxTinyUriCached;

    private ConcurrentMap<String, TinyUrl> shortUrlMap = new ConcurrentHashMap<>();

    private Cache<String, TinyUrl> cahced;

    @Override
    public String generatorTinyUrl(String longUrl) {
        TinyUrl tinyUrl = null;
        try {
            tinyUrl = cahced.get(longUrl, () -> {
                long id = idGenerator.incrId();
                String shortUrl = Base62.toBase62(id);
                TinyUrl tinyUrl1 = new TinyUrl(id, shortUrl, longUrl);
                shortUrlMap.put(shortUrl, tinyUrl1);
                return tinyUrl1;
            });
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
        return tinyUrl.getTinyUrl();
    }

    @Override
    public String getLongUrl(String shortUrl) {
        TinyUrl tinyUrl = shortUrlMap.get(shortUrl);
        if (null == tinyUrl) {
            return null;
        }
        return tinyUrl.getLongUrl();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        cahced = CacheBuilder.newBuilder()
                .maximumSize(maxTinyUriCached)
                .removalListener((RemovalListener<String, TinyUrl>) notification -> {
                    TinyUrl tinyUrl = notification.getValue();
                    if (null != tinyUrl) {
                        shortUrlMap.remove(tinyUrl.getTinyUrl());
                    }
                })
                .expireAfterWrite(Long.MAX_VALUE, TimeUnit.DAYS).build();
    }

    static class Base62 {
        private static final String BASE = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

        public static String toBase62(long num) {
            StringBuilder sb = new StringBuilder();
            do {
                int i = (int) (num % 62);
                sb.append(BASE.charAt(i));
                num /= 62;
            } while (num > 0);

            return sb.reverse().toString();
        }
    }
}
